package com.share.device.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.share.common.core.constant.SecurityConstants;
import com.share.common.core.context.SecurityContextHolder;
import com.share.common.core.domain.R;
import com.share.common.core.exception.ServiceException;
import com.share.common.core.utils.StringUtils;
import com.share.common.security.utils.SecurityUtils;
import com.share.device.domain.*;
import com.share.device.emqx.EmqxClientWrapper;
import com.share.device.emqx.ProtocolConvertUtil;
import com.share.device.emqx.constant.EmqxConstants;
import com.share.device.service.*;
import com.share.order.api.RemoteOrderInfoService;
import com.share.order.domain.OrderInfo;
import com.share.rule.api.RemoteFeeRuleService;
import com.share.rule.domain.FeeRule;
import com.share.user.api.RemoteUserService;
import com.share.user.domain.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DeviceServiceImpl implements IDeviceService {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private IStationService stationService;

    @Autowired
    private ICabinetService cabinetService;

    @Autowired
    private IMapService mapService;

    @Autowired
    private RemoteFeeRuleService remoteFeeRuleService;

    @Autowired
    private RemoteUserService remoteUserService;

    @Autowired
    private RemoteOrderInfoService remoteOrderInfoService;

    @Autowired
    private EmqxClientWrapper emqxClientWrapper;

    @Override
    public List<StationVo> nearbyStation(String latitude, String longitude, Integer radius) {
        //坐标，确定中心点
        // GeoJsonPoint(double x, double y) x 表示经度，y 表示纬度。
        GeoJsonPoint geoJsonPoint = new GeoJsonPoint(Double.parseDouble(longitude), Double.parseDouble(latitude));
        //画圈的半径,50km范围
        Distance d = new Distance(radius, Metrics.KILOMETERS);
        //画了一个圆圈
        Circle circle = new Circle(geoJsonPoint, d);
        //条件排除自己
        Query query = Query.query(Criteria.where("location").withinSphere(circle));
        List<StationLocation> stationLocationList = this.mongoTemplate.find(query, StationLocation.class);
        if (CollectionUtils.isEmpty(stationLocationList)) return null;

        //组装数据
        List<Long> stationIdList = stationLocationList.stream().map(StationLocation::getStationId).collect(Collectors.toList());
        //获取站点列表
        List<Station> stationList = stationService.list(new LambdaQueryWrapper<Station>().in(Station::getId, stationIdList).isNotNull(Station::getCabinetId));

        //获取柜机id列表
        List<Long> cabinetIdList = stationList.stream().map(Station::getCabinetId).collect(Collectors.toList());
        //获取柜机id与柜机信息Map
        Map<Long, Cabinet> cabinetIdToCabinetMap = cabinetService.listByIds(cabinetIdList).stream().collect(Collectors.toMap(Cabinet::getId, Cabinet -> Cabinet));

        //获取柜机id列表
        List<Long> feeRuleIdList = stationList.stream().map(Station::getFeeRuleId).collect(Collectors.toList());
        //获取柜机id与柜机信息Map
        List<FeeRule> data = remoteFeeRuleService.getFeeRuleList(feeRuleIdList).getData();
        Map<Long, FeeRule> feeRuleIdToFeeRuleMap = data.stream().collect(Collectors.toMap(FeeRule::getId, FeeRule -> FeeRule));

        List<StationVo> stationVoList = new ArrayList<>();
        stationList.forEach(item -> {
                    StationVo stationVo = new StationVo();
                    BeanUtils.copyProperties(item, stationVo);
                    // 计算距离
                    Double distance = mapService.calculateDistance(longitude, latitude, item.getLongitude().toString(), item.getLatitude().toString());
                    stationVo.setDistance(distance);

                    // 获取柜机信息
                    Cabinet cabinet = cabinetIdToCabinetMap.get(item.getCabinetId());
                    //可用充电宝数量大于0，可借用
                    if (cabinet.getAvailableNum() > 0) {
                        stationVo.setIsUsable("1");
                    } else {
                        stationVo.setIsUsable("0");
                    }
                    // 获取空闲插槽数量大于0，可归还
                    if (cabinet.getFreeSlots() > 0) {
                        stationVo.setIsReturn("1");
                    } else {
                        stationVo.setIsReturn("0");
                    }


                    // 获取费用规则
                    FeeRule feeRule = feeRuleIdToFeeRuleMap.get(item.getFeeRuleId());
                    stationVo.setFeeRule(feeRule.getDescription());

                    stationVoList.add(stationVo);
                }


        );

        return stationVoList;


    }
    @Override
    public StationVo getStation(Long id, String latitude, String longitude) {
        Station station = stationService.getById(id);
        StationVo stationVo = new StationVo();
        BeanUtils.copyProperties(station, stationVo);
        // 计算距离
        Double distance = mapService.calculateDistance(longitude, latitude, station.getLongitude().toString(), station.getLatitude().toString());
        stationVo.setDistance(distance);

        // 获取柜机信息
        Cabinet cabinet = cabinetService.getById(station.getCabinetId());
        //可用充电宝数量大于0，可借用
        if(cabinet.getAvailableNum() > 0) {
            stationVo.setIsUsable("1");
        } else {
            stationVo.setIsUsable("0");
        }
        // 获取空闲插槽数量大于0，可归还
        if (cabinet.getFreeSlots() > 0) {
            stationVo.setIsReturn("1");
        } else {
            stationVo.setIsReturn("0");
        }
// 获取费用规则
        FeeRule feeRule = remoteFeeRuleService.getFeeRule(station.getFeeRuleId()).getData();
        if (feeRule == null) {
            // 处理规则不存在的情况，例如记录日志或抛出业务异常
            log.error("FeeRule not found for id: {}", station.getFeeRuleId());
            throw new ServiceException("FeeRule configuration missing");
        }
        stationVo.setFeeRule(feeRule.getDescription());
        // 获取费用规则
//        FeeRule feeRule = remoteFeeRuleService.getFeeRule(station.getFeeRuleId()).getData();
//        stationVo.setFeeRule(feeRule.getDescription());
        return stationVo;
    }


    /**
     * 扫码充电接口
     * @param cabinetNo
     * @return
     */
    //扫码充电接口
    @Override
    public ScanChargeVo scanCharge(String cabinetNo) {
        //1 远程调用：根据当前登录用户id查询用户信息，
        // 从用户信息获取是否支持免押金充电
        R<UserInfo> userInfoR = remoteUserService.getInfo(SecurityContextHolder.getUserId());
        UserInfo userInfo = userInfoR.getData();
        //判断
        if(userInfo == null) {
            throw new ServiceException("获取用户信息失败");
        }
        //判断是否免押金
        if("0".equals(userInfo.getDepositStatus())) {
            throw new ServiceException("未申请免押金使用");
        }

        ScanChargeVo scanChargeVo = new ScanChargeVo();
        //2 远程调用：判断用户是否有未完成订单
        R<OrderInfo> orderInfoR = remoteOrderInfoService.getNoFinishOrder(SecurityUtils.getUserId());
        OrderInfo orderInfo = orderInfoR.getData();
        if(orderInfo != null) {//有 未完成订单
            String status = userInfo.getStatus();
            if("0".equals(status)) {
                scanChargeVo.setStatus("2");
                scanChargeVo.setMessage("有未归还充电宝，请归还后使用");
                return scanChargeVo;
            }
            if("1".equals(status)) {
                scanChargeVo.setStatus("3");
                scanChargeVo.setMessage("有未支付订单，去支付");
                return scanChargeVo;
            }
        }

        //3 从柜机里面获取最优充电宝
        AvailableProwerBankVo availableProwerBankVo =
                this.checkAvailableProwerBank(cabinetNo);
        if(null == availableProwerBankVo) {
            throw new ServiceException("无可用充电宝");
        }
        if(!StringUtils.isEmpty(availableProwerBankVo.getErrMessage())) {
            throw new ServiceException(availableProwerBankVo.getErrMessage());
        }

        //4 把选择最优充电宝弹出
        // 使用MQTT弹出充电宝
        // 生成借取指令，弹出充电宝
        JSONObject object = new JSONObject();
        object.put("uId", SecurityContextHolder.getUserId());//SecurityUtils.getUserId()
        object.put("mNo", "mm"+RandomUtil.randomString(8));
        object.put("cNo", cabinetNo);
        object.put("pNo", availableProwerBankVo.getPowerBankNo());
        object.put("sNo", availableProwerBankVo.getSlotNo());
        String topic = String.format(EmqxConstants.TOPIC_SCAN_SUBMIT, cabinetNo);
        String message = ProtocolConvertUtil.convertString(object);
        emqxClientWrapper.publish(topic, message);

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        //5 返回封装需要数据
        scanChargeVo.setStatus("1");
        return scanChargeVo;
    }

    @Autowired
    private ICabinetSlotService cabinetSlotService;

    @Autowired
    private IPowerBankService powerBankService;
    //获取柜机充电宝信息
    private AvailableProwerBankVo checkAvailableProwerBank(String cabinetNo) {
        //1创建AvailablePowerBankVo对象
        AvailableProwerBankVo availableProwerBankVo = new AvailableProwerBankVo();
        //2根据cabinetNo柜机编号查询柜机信息
        Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);
        //3判断柜机中可用的充电宝的数量是否大于0
        Integer availableNum = cabinet.getAvailableNum();
        if(availableNum == 0) {
            availableProwerBankVo.setErrMessage("无可用充电宝");
            return availableProwerBankVo;
        }
        //4根据柜机的id查询插槽列表，返回一个list集合
        LambdaQueryWrapper<CabinetSlot> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CabinetSlot::getCabinetId, cabinet.getId());
        List<CabinetSlot> cabinetSlotList = cabinetSlotService.list(wrapper);
        //5从返辉插槽列表list集合，获取对应的充电宝id集合
        List<Long> powerBankIdList = cabinetSlotList.stream()
                .filter(item -> null != item.getPowerBankId())
                .map(CabinetSlot::getPowerBankId)
                .collect(Collectors.toList());
        //6根据充电宝id列表查询对应的充电宝信息
        LambdaQueryWrapper<PowerBank> wrapper1 = new LambdaQueryWrapper<>();
        wrapper1.in(PowerBank::getId, powerBankIdList);
        wrapper1.eq(PowerBank::getStatus,"1");
        List<PowerBank> powerBanklist = powerBankService.list(wrapper1);
        //判断集合不为空
        if(CollectionUtils.isEmpty(powerBanklist)) {
            availableProwerBankVo.setErrMessage("无可用充电宝");
            return availableProwerBankVo;
        }

        //7把上一步获取充电宝信息集合进行排序（根据电量降序）
        if(powerBanklist.size() > 1) {
            Collections.sort(powerBanklist,
                    (o1,o2)->o2.getElectricity().compareTo(o1.getElectricity()));
        }
        //8获取电量最多的充电宝信息
        PowerBank powerBank = powerBanklist.get(0);
        //9获取电量最多的充电宝对应的插槽信息
        CabinetSlot cabinetSlot = cabinetSlotList.stream()
                .filter(item -> null != item.getPowerBankId() && item.getPowerBankId().equals(powerBank.getId()))
                .collect(Collectors.toList()).get(0);
        //10锁定插槽（更新插槽状态）
        cabinetSlot.setStatus("2");
        cabinetSlotService.updateById(cabinetSlot);

        //设置返回对象
        availableProwerBankVo.setPowerBankNo(powerBank.getPowerBankNo());
        availableProwerBankVo.setSlotNo(cabinetSlot.getSlotNo());
        //返回需要vo数据
        return availableProwerBankVo;

    }
}
